import os
import subprocess
import time
import requests
import json
import re
import uuid
from requests import utils
from tqdm import tqdm
from bs4 import BeautifulSoup
import threading
import qrcode
from tkinter import Tk, Canvas, PhotoImage

GET_OAUTH_KEY_URL = "https://passport.bilibili.com/x/passport-login/web/qrcode/generate"
WAIT_FOR_LOGIN_URL = "https://passport.bilibili.com/x/passport-login/web/qrcode/poll"
GET_PLAY_API_URL = "https://api.bilibili.com/pugv/player/web/playurl"
COURSE_PREFIX = "https://www.bilibili.com/cheese/play/"

cookies_dict = {}
header = {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Connection": "keep-alive",
    "Content-Type": "application/json",
    "Referer": "https://www.bilibili.com/",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"
}


# 获取登录的key和要扫描二维码的地址
def get_oauth_key():
    response = requests.get(GET_OAUTH_KEY_URL, headers=header)
    if response.status_code == 200:
        data = response.json()
        return data['data']['url'], data['data']['qrcode_key']
    return None, None


# 等待登录
def wait_for_login(oauth_key):
    while True:
        response = requests.get(WAIT_FOR_LOGIN_URL, {"qrcode_key": oauth_key},headers=header)
        if response.status_code == 200:
            data = response.json()
            if data['data']['code'] == 0:
                print("登录成功")
                return response.cookies
            print(data['data']['message'])
            time.sleep(1)


# 展示二维码登录图片
def show_qr_code(url):
    # 生成二维码
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=10,
        border=5,
    )
    qr.add_data(url)
    qr.make(fit=True)
    img = qr.make_image(fill_color="black", back_color="white")
    qrcode_file = f'./download/temp/{key}.png'
    img.save(qrcode_file)
    # 创建一个Tkinter窗口
    root = Tk()
    root.title("请使用客户端扫描二维码登录")

    # 加载二维码图片并将其加载到画布上
    photo = PhotoImage(file=qrcode_file)
    canvas = Canvas(root, width=photo.width(), height=photo.height())
    canvas.pack()
    canvas.create_image(0, 0, image=photo, anchor="nw")
    root.mainloop()
    os.remove(qrcode_file)


# 获取课程列表
def get_sections_list(session_id):
    html_content = requests.get(COURSE_PREFIX + session_id, {}, headers=header, cookies=cookies_dict)
    soup = BeautifulSoup(html_content.text, 'html.parser')
    js_code = soup.find_all('script')[1].text
    if not js_code.startswith('window.__EduPlayPiniaState__'):
        print('获取课程列表失败,1秒后重试...')
        time.sleep(1)
        return get_sections_list(session_id)
    json_raw = js_code.replace('window.__EduPlayPiniaState__="', '').replace('\\"', '"').replace('\\\\\"', '\\\\\\"')[:-1]
    json_data = json.loads(json_raw, strict=False)
    return json_data['index']['sections'][0]['episodes']


# 解析时间为秒
def parse_time_2_sec(s):
    duration_match = re.search(r'(\d{2}):(\d{2}):(\d{2})', s)
    if not duration_match:
        return 0
    hours, minutes, seconds_milliseconds = duration_match.groups()
    seconds = int(hours) * 3600 + int(minutes) * 60
    seconds += int(seconds_milliseconds)
    return seconds


# 获取视频信息,返回音频地址,视频地址
def get_video_info(ep_id, avid, cid):
    request_args = {
        "avid": avid,
        "cid": cid,
        "qn": "80",
        "fnver": "0",
        "fnval": "16",
        "fourk": "1",
        "gaia_source": "",
        "from_client": "BROWSER",
        "ep_id": ep_id,
        "session": str(uuid.uuid4()).replace('-', ''),
        "drm_tech_type": "2",
    }

    response = requests.get(GET_PLAY_API_URL, request_args, headers=header, cookies=cookies_dict)
    if response.status_code == 200:
        json_data = json.loads(response.text)
        return json_data['data']['dash']['audio'][0]['base_url'], json_data['data']['dash']['video'][0]['base_url']


# 下载文件
def download_file(url, save_path, desc):
    # 发送GET请求获取文件大小
    response = requests.head(url, headers=header, cookies=cookies_dict)
    file_size = int(response.headers.get('content-length', 0))

    # 创建一个tqdm进度条实例
    progress_bar = tqdm(total=file_size, unit='B', unit_scale=True, desc=f'Downloading {desc}')

    def update_progress(chunk_size=1, total=None):
        if total is None:
            total = file_size
        progress_bar.update(chunk_size)

    with requests.get(url, stream=True, headers=header, cookies=cookies_dict) as r:
        r.raise_for_status()
        with open(save_path, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192):
                if chunk:  # filter out keep-alive new chunks
                    f.write(chunk)
                    update_progress(len(chunk))
    progress_bar.close()


if not os.path.exists('./download/temp'):
    os.makedirs('./download/temp')

if not os.path.exists('./bilibili.session'):
    url, key = get_oauth_key()
    new_thread = threading.Thread(target=show_qr_code, args=(url,))
    new_thread.start()
    cookies_dict = utils.dict_from_cookiejar(wait_for_login(key))
    with open('bilibili.session', 'w', encoding='utf-8') as file:
        file.write(json.dumps(cookies_dict, indent=4, ensure_ascii=False))
else:
    with open('bilibili.session', 'r', encoding='utf-8') as file:
        cookies_dict = json.loads(file.read())

print('请输入要下载的课程序号,只需要最后的ID')
print('例如你的课程地址是https://www.bilibili.com/cheese/play/ss360')
print('那么你的课程ID是 ss360 ')
input_id = input('请输入要下载的课程序号: ')
sections_list = get_sections_list(input_id)
index = 0
for sections in tqdm(sections_list):
    # 基础参数配置
    index += 1
    ep_id = sections['id']
    avid = sections['aid']
    cid = sections['cid']
    title = sections['title'].replace(':', '_')

    # 获取音频和视频的链接，并设置本地保存的文件名
    filename_prefix = uuid.uuid4()
    audio_url, video_url = get_video_info(ep_id, avid, cid)
    audio_file = f"./download/temp/{filename_prefix}_audio.mp4"
    video_file = f"./download/temp/{filename_prefix}_video.mp4"
    output_file = f"./download/{index}.{title}.mp4"

    download_file(audio_url, audio_file, f'{title} audio [1/3][{index}/{len(sections_list)}]')
    download_file(video_url, video_file, f'{title} video [2/3][{index}/{len(sections_list)}]')

    cmd_line = f'ffmpeg -i "{video_file}" -i "{audio_file}" -c:v copy -map 0:v:0 -map 1:a:0 -shortest -y "{output_file}"'
    encode_progress_bar = tqdm(total=sections['duration'], unit='second', desc=f'Encoding    {title} video [3/3][{index}/{len(sections_list)}]')
    process = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf-8')
    # 进度条展示编码进度
    for line in process.stdout:
        if line.startswith('size='):
            time_length = parse_time_2_sec(line)
            encode_progress_bar.update(time_length - encode_progress_bar.n)
    encode_progress_bar.close()

    os.remove(audio_file)
    os.remove(video_file)
